/*
*** FILE NAME   : example.c
*** DESCRIPTION : This program is an example program using OpenGL.
*** DATE        : June 2010
*** WRITTEN By  : Stephen Durfey and Ronald Ngatuni
*/

#include <stdio.h>              // standard C libraries
#include <stdlib.h>
#include <cstring>
#include <math.h>
#include <time.h>
#include <string.h>
#include <GL/freeglut.h>            // GLUT library
#include <vector>
#include <fstream>
#include <iostream>
#include <algorithm>

using namespace std;


//@@***********************************************************************************@@
// Constants
#define WINDOW_XS 256					// Window size
#define WINDOW_YS 256
#define WINDOW_NAME "OpenGL example"	// Window name
#define pi 3.14159265

//@@***********************************************************************************@@
// Structures
typedef struct pt
{
   GLint x, y;
}MyPoint;

//sobel operator vector
double Gx[9] = {
	-1,0,1,
	-2,0,2,
	-1,0,1
};

double Gy[9] = {
	1,2,1,
	0,0,0,
	-1,-2,-1
};

//@@***********************************************************************************@@
// Global Variables
MyPoint strtPt, endPt;
char* fileName = "line6.ppm";
//char* fileName = "line2.ppm";

//vector<vector<float> > image;
float image[256][256];
vector<pt> edgePixels;
int maxIntensity, width, height;
float LineIntercept, LineGradient;
float rho, theta;
bool houghCalled = false;


//@@***********************************************************************************@@
// Function prototypes
void reshape_handler(int width, int height);
void init_setup(int width, int height, char *windowName);
void display_func(void);
void keyboard_func(unsigned char c, int x, int y);
void readImage();
void displayImage();
void doMedianFiltering(int square);
void doSobelOperator(int square);
bool isValid(int x, int y);
void getNeighbors(int x, int y, float dest[], int square,float temp[256][256] /*vector<vector<float> > temp*/);
float getPixel(int x, int y, float temp[256][256]/*vector<vector<float> > temp*/);
void produceBinaryImage(int threshold);
void houghTransform();
void drawLine();
void displayAcc(int [360][1024]);
void drawCoordinateGrid();

//@@***********************************************************************************@@
int main(int argc, char **argv)
{
	glutInit(&argc, argv);

	init_setup(WINDOW_XS, WINDOW_YS, WINDOW_NAME);

	readImage();
	glutDisplayFunc(display_func);
	glutKeyboardFunc(keyboard_func);
	
	glutMainLoop();

	

	return 1;
}	// end of main()

//@@***********************************************************************************@@
void reshape_handler(int width, int height)
{
	glViewport(0, 0, width, height);							// sets the viewport
	glMatrixMode(GL_PROJECTION);								// projection matrix
	glLoadIdentity();											// loads identity matrix
	gluOrtho2D(0.0, (GLdouble)width, 0.0, (GLdouble)height);	// 2D orthographic projection
}	// end of reshape_handler()

//@@***********************************************************************************@@
void init_setup(int width, int height, char *windowName)
{
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);  // single buffer, rgb color
	//glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);  // double buffer, rgb color, for animzation
	glutInitWindowSize(width, height);			  // init. window size
	glutInitWindowPosition(5, 5);				  // init. window position
	glutCreateWindow(windowName);				  // window name
	glutReshapeFunc(reshape_handler);		      // sets the reshape call back
}	// end of init_setup()

//@@***********************************************************************************@@
void display_func(void)
{
	glClearColor(1.0, 1.0, 1.0, 1.0);   // background color (white)
	glClear(GL_COLOR_BUFFER_BIT);       // clearing the buffer not to keep the color

	displayImage();
	drawCoordinateGrid();
	
	if(houghCalled)
	{
		cout << "called\n";
		drawLine();
	}


}	// end of display_func()

//@@***********************************************************************************@@
void keyboard_func(unsigned char c, int x, int y)
{
	switch(c)
	{
		case 'M' :
		case 'm' :
			doMedianFiltering(3);
			glutPostRedisplay();
			break;

		case 's' :
		case 'S' :
			doSobelOperator(3); //do sobel on a three by three
			glutPostRedisplay();
			break;
		case 'b':
		case 'B':
			produceBinaryImage(1); //produce BinaryImage with 1 as threshold value;
			glutPostRedisplay();
			break;
		case 'h':
		case 'H':
			houghTransform();
			glutPostRedisplay();
			break;
		case 'Q' :
		case 'q' :	
			printf("Good Bye !\n");
			exit(0);				 // terminates the program
	}  // end of switch
}	// end of keyboard_func()

void drawCoordinateGrid()
{
	glColor3f(0.0,0.0,255);

	glBegin(GL_LINES);
	{
		glVertex2i(width/2,0);
		glVertex2i(width/2,height);
		glVertex2i(0,height/2);
		glVertex2i(width, height/2);
	}
	glEnd();
	glFlush();


}

void readImage()
{
	ifstream inData(fileName);
	string fileType;
	char comment[80];
	float r, g, b, average;
	vector<float> temp;

	cout << "function called\n";


	if(!inData)
	{
		cout << "file not open\n";
		exit(1);
	}


	inData >> fileType;
	//inData.ignore();

	//inData.getline(comment, 80);
	//cout << comment << endl;

	inData >> width >> height;
	inData >> maxIntensity;



	for(int row = 0; row < width; row++)
	{
		for(int column = 0; column < height; column++)
		{
			inData >> r;
			inData >> g;
			inData >> b;

			average = ((r + g + b) / 3.0) / maxIntensity;
	
			//temp.push_back(average);
			image[row][column] = average;

		}

		//image.push_back(temp);
		//temp.clear();
	}

}

void displayImage()
{
	float color;

	glBegin(GL_POINTS);
	{
		for(int row = 0; row < width; row++)
		{
			for(int column = 0; column < height; column++)
			{
				color = image[row][column];
				glColor3f(color, color, color);
				glVertex2f(column, WINDOW_YS-row);
			}
		}
	}
	glEnd();
	glFlush();
}

void doMedianFiltering(int square)
{
	//vector<vector<float> > temp = image;
	float temp[256][256];
	float neighbors[9], median;


	for(int row = 0; row < height; row++)
	{
		for(int column = 0; column < width; column++)
		{
			//cout << "old: " << temp[column][row];
			getNeighbors(column, row, neighbors, square, image);
			std::sort(neighbors,neighbors + 9); //sort the array

			median = neighbors[(8/2)]; //find median
			
			temp[column][row] = median;	
			
		}
	}

	memcpy(image,temp,sizeof(image));
	cout <<"completed" << endl;
}
void doSobelOperator(int square)
{
	float temp[256][256];
	float neighbors[9];
	double SUM, sumX, sumY;
	ofstream outData("sobel");

	for(int row = 0; row < height; row++)
	{
		for(int column = 0; column < width; column++)
		{
			getNeighbors(column, row, neighbors, square, image);
			sumX = 0; sumY = 0;
			//get X gradient approximation
			for(int i = 0; i < square * square; i++)
			{
				sumX += Gx[i] * neighbors[i];
			}

			//get Y gradient approximation
			for(int i = 0; i < square * square; i++)
			{
				sumY += Gy[i] * neighbors[i];
			}

			SUM = sqrt(pow(sumX,2.0) + pow(sumY,2.0));
			temp[column][row] = SUM; // the summation of sobel operator to that pixel

			outData << "x: " << column << "     y:" << row << "      value:" << temp[column][row] << endl;


			
		}
	}

	memcpy(image,temp,sizeof(image));
	cout <<"completed" << endl;
}

bool isValid(int x, int y)
{
	return (x >= 0 && x < width && y >= 0 && y < height);
}

void getNeighbors(int x, int y, float dest[], int square,float temp[256][256] /*vector<vector<float> > temp*/)
{
	if (square == 3)
	{
		dest[0] = getPixel(x-1,y+1,temp);
		dest[1] = getPixel(x  ,y+1,temp);
		dest[2] = getPixel(x+1,y+1,temp);
		dest[3] = getPixel(x-1,y  ,temp);
		dest[4] = getPixel(x  ,y  ,temp);
		dest[5] = getPixel(x+1,y  ,temp);
		dest[6] = getPixel(x-1,y-1,temp);
		dest[7] = getPixel(x  ,y-1,temp);
		dest[8] = getPixel(x+1,y-1,temp);
	}
}

float getPixel(int x, int y,float temp[256][256]/*vector<vector<float> > temp*/)
{
	if(!isValid(x,y))
		return 0;

	return temp[x][y];
}
void produceBinaryImage(int threshold)
{
	float temp[256][256]; //temp hold the image
	ofstream outData("binary");
	int modRow, modCol;
	
	for(int row = 0; row < height; row++)
	{
		for(int column = 0; column < width; column++)
		{
			if(image[row][column] >= threshold)
			{
				if(row > (height/2))
					modRow = (row - (height/2))*-1;
				else
					modRow = (height/2) - row;
				if(column <= (width/2))
					modCol = ((width/2) - column) * -1;
				else
					modCol = column - (width/2);

				outData << "original values: " << "x: " << column << "     y:" << row << "      value:" << image[column][row] << endl;
				outData << "modified values: " << "x: " << modCol << "     y:" << modRow << "      value:" << image[column][row] << endl;
				temp[row][column] = 1;// white
				pt temp; temp.x = modCol; temp.y = modRow;
				edgePixels.push_back(temp);
			}
				
			else
				temp[row][column] = 0; //black
		}
	}
	memcpy(image,temp,sizeof(temp));
}

void houghTransform()
{
	int accumulator[180][1024] = {{0},{0}};
	ofstream outData("rho-theta");

	int highest = 0;

	for (int i = 0; i < edgePixels.size(); i++)
	{
		for (int index = -90; index < 90; index++)
		{
			rho = (float)edgePixels[i].x * cos(index*pi/180) + (float)edgePixels[i].y * sin(index*pi/180);

			if(rho > 0)
			{
				accumulator[index+90][(int)rho+1]++;
				outData << "theta: " << index << "     x:" << (float)edgePixels[i].x << "     y:" << (float)edgePixels[i].y << "     rho:" << rho << endl;
			}

		}
	}

	highest = accumulator[0][0];
	rho = 0;
	theta = -90;

	for(int i = 0; i < 180; i++)
	{
		for(int k = 0; k < 1024; k++)
		{
			if(accumulator[i][k] > highest)
			{
				rho = k;
				theta = i-90;
				highest = accumulator[i][k];
			}
		}
	}

	houghCalled = true;


	cout << "rho: " << rho << endl << "theta: " << theta << endl;
	cout << "count: " << accumulator[(int)theta+90][(int)rho] << endl;
	displayAcc(accumulator);
		
}
void drawLine()
{

	int y = (rho) / sin(theta*pi/180) + 256 ;
	int x = -((rho)/sin(theta*pi/180)) * -(tan(theta*pi/180)) + 256;
	//int x = (y-(rho/sin(theta * pi/180)))/(-(cos(theta * pi/180)/sin(theta * pi/180)));
		cout <<"x" <<x << endl << "y:" << y << endl;
	glColor3f(1.0,0.0,0.0);
	glBegin(GL_LINES);
		//if(y >= 0)
		//{
			//glVertex2d(0,y - WINDOW_YS);
			//glVertex2d(x,0-WINDOW_YS);
			glVertex2d(0,y);
			glVertex2d(x,0);
		//}
		//else
		//{
		//	glVertex2d(0,WINDOW_YS +y);
		//	glVertex2d(x,0);
		//}
		//glVertex2d(0,y);
		//glVertex2d(x,0);
	glEnd();
	glFlush();


}

void displayAcc(int accumulator[360][1024])
{
	ofstream outData("accu");

	for(int i = 0; i < 180; i++)
	{
		for(int j = 0; j < 1024; j++)
			outData << accumulator[i][j] << " ";

		outData << endl;
	}

}

